// const str = console.log`hello world`
// const name = 'Tom'
// const gender = true

// function myTagFunc(string, name, gender) {
//     // console.log(string)
//     // console.log(name)
//     // console.log(gender)
//     // return 123
//     const sex = gender ? 'man' : 'woman'
//     return string[0] + name + string[1] + sex + string[2]
// }
// const result = myTagFunc`hey, ${name} is a ${gender}.`
// console.log(result)

// const message = 'Error: foo is not defined.'
// // 判断字符串是否以Error开头
// // 判断字符串是否以.结尾
// // 判断字符串中是否包含foo
// console.log(
//     // message.startsWith('Error') // true
//     // message.endsWith('.') // true
//     message.includes('foo') //true
// )

// function foo(name='jerry', enable = true) {
//     console.log(name);
//     console.log(enable)
// }
// foo(undefined, false) // fasle
// // foo() // true

// function foo (...args) {
//     console.log(args)
// }
// foo(1,2,3,4) // [ 1, 2, 3, 4 ]

// const arr = ['foo', 'bar', 'baz']
// console.log.apply(console, arr) // foo bar baz
// console.log(...arr) // ES2015写法 foo bar baz

// 箭头函数

// function inc(number) {
//     return number + 1
// }
// const inc = n => n+1
// console.log(inc(100))

// const person = {
//     name: 'Tom',
//     // sayHi: function() {
//     //     console.log(this.name) // Tom
//     // }
//     sayHi: () => {
//         console.log(this.name) // undefined
//     },
//     sayHiAsync: function() {
//         // const _this = this
//         // setTimeout(function(){
//         //     console.log(_this.name)
//         // }, 1000)
//         setTimeout(()=> {
//             console.log(this.name)
//         }, 1000)
//     }  
// }
// person.sayHi()
// person.sayHiAsync()

// const bar = '123'
// const obj = {
//     foo: 123,
//     // bar: bar
//     bar,
//     // method: function() {
//     //     console.log('method')
//     // }
//     method() {
//         console.log('method')
//     }
// }
// console.log(obj) //{ foo: 123, bar: '123' }

// Object.assign方法
// const source1 = {
//     a: 123,
//     b: 234
// }

// const target = {
//     a: 456,
//     c: 789
// }

// const res =Object.assign(target, source1)
// console.log(res); // { a: 123, c: 789, b: 234 }

// function func(obj) {
//     // obj.name = 'func obj'
//     // console.log(obj) // {name: 'func obj'}
//     const funcObj = Object.assign({},obj)
//     funcObj.name = 'func obj'
//     console.log(funcObj) // {name: 'func obj'}
// }

// const obj = { name: 'global obj'}
// func(obj)
// console.log(obj) // {name: 'global obj'}

// console.log(
//     // Object.is(+0, -0) // false
//     Object.is(NaN, NaN) // true
// )

// const person = {
//     name: 'zce',
//     age: 18
// }
// // 参数：代理的目标对象，代理的处理对象
// const personProxy = new Proxy(person, {
//     // 参数：代理的目标对象， 外部所访问的这个属性名
//     get(target, property) {
//         // 返回值将会作为外部访问这个属性得到的结果
//         // console.log(target, property); 
//         // return 100
//         return property in target ? target[property] : 'defalut'
//     },
//     // 参数：代理的目标对象, 写入的属性名称，写入的属性值
//     set(target, property, value) {
//         // 为我们的代理目标去设定指定的属性
//         if(property === 'age') {
//             if(!Number.isInteger(value)) {
//                 throw new TypeError(`${value} is not an int`)
//             }
//         }
//         target[property] = value
//         // console.log(target, property, value)
//     }
// })
// // console.log(personProxy.name); // { name: 'zce', age: 18 } 'name'
// // console.log(personProxy.xxxx) // defalut

// // personProxy.gender = true // { name: 'zce', age: 18 } 'gender' true
// personProxy.age = 'hhh' // throw new TypeError(`${value} is not an int`)


// const person = {
//     name: 'zce',
//     age: 18
// }
// const personProxy = new Proxy(person, {
//     deleteProperty(target, property) {
//         // 对当前这个对象进行delete操作时，会自动执行
//         console.log('delete', property);
//         delete target[property]
//     }
// })

// delete personProxy.age
// console.log(person); // delete age { name: 'zce' }

// const list = []
// const listProxy = new Proxy(list, {
//     set(target, property, value) {
//         // 监视数据写入 
//         console.log('set', property, value);
//         target[property] = value
//         return true // 表示写入成功
//     }
// })

// listProxy.push(100)

// function foo() {
//     setTimeout(() => this.a = 1, 0)
//     console.log("<<<<<<<<<<<<<<" + this.a)
// }
// function doFoo(fn) {
//     this.a = 4
//     console.log(">>>>>>>this: " + this.a)
//     fn()
// }
// var obj = {
//     a: 2,
//     foo: foo
// }
// doFoo(obj.foo)
// setTimeout(obj.foo, 0)
// obj.foo
// console.log(obj.foo);

// const obj = {
//     "name": 'lyj',
//     "age": 18
// }

// // console.log('name' in obj)
// // console.log(delete obj['age'])
// // console.log(Object.keys(obj))

// console.log(Reflect.has(obj, 'name'))
// console.log(Reflect.deleteProperty(obj, 'age'))
// console.log(Reflect.ownKeys(obj))

// function Person(name) {
//     this.name - name
// }

// Person.prototype.say = function() {
//     console.log(`hi, my name is ${this.name}`)
// }

// class Person {
//     constructor(name) {
//         this.name = name
//     }

//     say() {
//         console.log(`hi, my name is ${this.name}`)
//     }
// }

// class Student extends Person{
//     // Student类型中会拥有Person里面的所有成员
//     constructor(name ,number) {
//         // super 始终指向父类，调用它就是调用了父类的构造函数
//         super(name) 
//         this.number = number
//         }

//     hello() {
//         super.say()
//         console.log(`my school is ${this.number}`)
//     }
// }

// const s = new Student('jack', '100')
// s.hello()

// const s = new Set()
// // add方法可以返回这个集合本身，所以它可以链式的调用
// // 如果添加了之前已经添加过的值，那这个值就会被忽略掉
// s.add(1).add(2).add(3).add(4).add(2)
// console.log(s) // Set { 1, 2, 3, 4 }
// // 遍历
// // s.forEach(i => console.log(i))
// for(let i of s) {
//     console.log(i)
// }
// // size获取整个集合的长度
// console.log(s.size)
// // has 判断集合当中是否存在某个特定的值
// console.log(s.has(100))  // false
// // delete 删除某一指定值
// console.log(s.delete(s)) // true
// //clear 清除全部内容
// s.clear()

// // 数组去重
// const arr = [1, 2, 3, 1, 4, 1]
// // const result = new Set(arr)
// // console.log(result) // Set { 1, 2, 3, 4 }
// // 如果要重新得到一个数组
// // ES2015中的Array.from把set在吃转化为array
// const result = Array.from(new Set(arr)) //[ 1, 2, 3, 4 ]
// // 或者 ... 展开
// const result = [ ... new Set(arr)] // [ 1, 2, 3, 4 ]
// console.log(result)

// const obj = []
// obj[true] = 'value'
// obj[123] = 'value'
// obj[{a: 1}] = 'value'
// console.log(Object.keys(obj)) // [ '123', 'true', '[object Object]' ]

// const m = new Map()
// const tom = { name: 'tom' }
// m.set(tom, 90)
// console.log(m) // Map { { name: 'tom' } => 90 }
// console.log(m.get(tom)) // 90
// // m.has() 判断某一个键是否存在
// // m.delete() 去删除掉某一个键
// // m.clear() 去清空所有的键值对
// // 遍历
// m.forEach((value, key)=> {
//     console.log(value, key)
// })

// const s = Symbol()
// console.log(s)
// console.log(typeof s) // symbol

// const obj = {}
// // Symbol的值是独一无二的
// obj[Symbol()] = '123'
// obj[Symbol()] = '456'
// // 或者
// const obj = {
//     [Symbol()]: '123',
//     [Symbol()]: '456'
// }
// console.log(obj) // { [Symbol()]: '123', [Symbol()]: '456' }

// // a.js======================
// const name = Symbol()
// const person = {
//     [name]: 'zce',
//     say() {
//         console.log(this[name])
//     }
// }

// // b.js ================
// // person[Symbol()] // 不被允许
// person.say()

// // for维护了一个全局的注册表和symbol值提供了一一对应的关系，但需要注意的是内部维护的是字符串和symbol之间的对应关系
// const s1 = Symbol.for('foo')
// const s2 = Symbol.for('foo')
// console.log(s1===s2) // true

// // 如果传入的不是字符串，这个方法会自动tostring转化为字符串
// console.log(
//     Symbol.for(true)===Symbol.for('true') //true
// )

// // console.log(Symbol.iterator)
// // console.log(Symbol.hasInstance)

// const obj = {
//     [Symbol.toStringTag]: 'XObject'
// }
// console.log(obj.toString()) // [object XObject]

// // Symbol值去作为对象的属性名，那这个属性我们用过传统的for...in循环是无法拿到的

// const obj = {
//     [Symbol()]: 'symbol value',
//     foo: 'normal value'
// }
// for(var key in obj) {
//     console.log(key) // foo
// }
// // 通过Object.keys的方法也是获取不到的
// console.log(Object.keys(obj)) // [ 'foo' ]
// // JSON.stringify去序列化Symbol也会被忽略掉
// console.log(JSON.stringify(obj)) // {"foo":"normal value"}
// // 以上种种让我们意识到Symbol特别适合作用对象的私有属性

// // 这个方法类似Object.keys方法，获取到的全是Symbol类型的属性名
// console.log(Object.getOwnPropertySymbols(obj)) // [ Symbol() ]

// const arr = [100, 200, 300, 400, 500]
// for(const item of arr) {
//     console.log(item)
// }

// arr.forEach(item => {
//     console.log(item)
// })

// for(const item of arr) {
//     if(item > 200) {
//         break
//     }
// }

// // arr.forEach() //不能终止遍历
// // arr.some()
// // arr.every()

// co'(nst s = new Set(['foo', 'bar'])
// for(item of s) {
//     console.log(item)
// }

// const m = new Map()
// m.set('foo', '123')
// m.set('bar', '345')
// // for (const item of m) {
// //     console.log(item) // 遍历出来是是key-value的键值对数组
// // }
// for (const [key, value] of m) {
//     console.log(key, value)
// })'

// const set = new Set(['foo', 'bar', 'baz'])
// const iterator = set[Symbol.iterator]()
// console.log(iterator.next())
// console.log(iterator.next())
// console.log(iterator.next())
// console.log(iterator.next())

// const obj = {
//     store:  ['foo', 'bar', 'baz'],
//     // Iterable
//     [Symbol.iterator]: function() {
//         let index = 0
//         const self = this
//         return {
//             // Iterator
//             next: function() {
//                 const result = {
//                     // IterationResult
//                     value: self.store[index],
//                     done: index >= self.store.length
//                 }
//                 index++
//                 return result
//             }
//         }
//     }
// }
// for (const item of obj) {
//     console.log('---', item)
// }

// const todos = {
//     life: ['吃饭', '睡觉', '打豆豆'],
//     learn: ['语文', '数学', '外语'],
//     work: ['喝茶'],

//     // 对外提供一个统一遍历的接口
//     each: function(callback) {
//         const all = [].concat(this.learn, this.life, this.work)
//         for (const item of all) {
//             callback(item)
//         }
//     },
//     [Symbol.iterator]: function() {
//         const all = [...this.learn, ...this.life, ...this.work]
//         let index = 0
//         return {
//             next: function() {
//                 return {
//                     value: all[index],
//                     done: index++ >= all.length
//                 }
//             }
//         }
//     }
// }

// // for (const item of todos.life) {
// //     console.log(item)
// // }
// // for (const item of todos.learn) {
// //     console.log(item)
// // }
// // for (const item of todos.work) {
// //     console.log(item)
// // }
// // todos.each(function(item) {
// //     console.log(item)
// // })
// // 好！实现迭代逻辑就可以，不用就方法写方法，全部对象都是通用的
// for (const item of todos) {
//     console.log(item)
// }

// // Generator函数 
// // function * foo() {
// //     console.log('foo')
// //     return 100
// // }
// // const result = foo()
// // console.log(result.next())

// function * foo() {
//     console.log('1111')
//     yield 100 // 不会结束函数
//     console.log('22222')
//     yield 200
//     console.log('33333')
//     yield 300
// }

// const generator = foo()
// // 生成器函数他会自动帮我们返回一个生成器对象，我们调用这个对象的next()方法，才会让这个函数的函数体开始执行，执行过程中一旦遇到了yield的关键词，函数执行就会被暂停下来，而且yield后面的值，将会作为next的结果返回，next再次执行的时候会从上一次暂停的地方继续开始执行
// // 最大的特点就是惰性执行
// console.log(generator.next())
// console.log(generator.next())
// console.log(generator.next())
// console.log(generator.next())

// // Generator应用
// // 案例1: 发号器
// function * createIdMaker() {
//     let id = 1
//     while(true) {
//         yield id++
//     }
// }

// const  idMarker = createIdMaker()
// console.log(idMarker.next().value)

// // 使用生成器函数来实现对象的的itrator方法
// // 案例2: 使用Generator函数实现iterator方法
// const todos = {
//     life: ['吃饭', '睡觉', '打豆豆'],
//     learn: ['语文', '数学', '外语'],
//     work: ['喝茶'],
//     [Symbol.iterator]: function * () {
//         const all = [...this.learn, ...this.life, ...this.work]
//         for (const item of all) {
//             yield item
//         }
//     }
// }

// for (const item of todos) {
//     console.log(item)  
// }

// const arr = ['foo', 1, NaN, false]
// // console.log(arr.indexOf('foo'))
// // console.log(arr.indexOf('bar'))
// // // indexOf方法啊不能去查找数组当中的NaN
// // console.log(arr.indexOf('NaN')) // -1
// console.log(arr.includes(NaN)) // true

// // console.log(Math.pow(2, 10))
// console.log(2**10)

// const obj = {
//     foo: 'value1',
//     bar: 'value2'
// }
// // Object.values
// console.log(Object.values(obj)) // [ 'value1', 'value2' ]

// // Object.entries 将键值对转化为数字的形式
// console.log(Object.entries(obj)) // [ [ 'foo', 'value1' ], [ 'bar', 'value2' ] ]
// for (const [key, value] of Object.entries(obj)) {
//     console.log(key, value)
// }
// console.log(new Map(Object.entries(obj)))

// // 帮我们去获取对象当中属性的完整描述信息,配合我们ES2015的get，set去使用
// const p1 = {
//     firstName: 'leo',
//     lastName: 'Wang',
//     get fullName() {
//         return this.firstName + " " + this.lastName
//     }
// }
// // console.log(p1.fullName)

// // const p2 = Object.assign({}, p1)
// // p1.firstName = 'lyj'
// // console.log(p2)

// const descriptors = Object.getOwnPropertyDescriptors(p1)
// // console.log(descriptors)
// const p2 = Object.defineProperties({}, descriptors)
// p2.firstName = 'lyj'
// console.log(p2.fullName)


const books = {
    html: 5,
    css: 16,
    javaScript: 128
}

for (const [name, count] of Object.entries(books)) {
    // console.log(name, count )
    console.log(`${name.padEnd(16, '-')} | ${count.toString().padStart(3, '0')}`)
}